/*
 *  ekk_socket.c
 *  EKK_Player_Lib
 *
 *  Created by hsh on 11-8-18.
 *  Copyright 2011 xunlei. All rights reserved.
 *
 */

#include "ekk_subtitle/ekk_socket.h"
#include "ekk_base/ekk_logger.h"
#if EKK_ENABLE_SUBTITLE

#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <fcntl.h>

#define MAXDATASIZE (4096)

#define MAX_SOCKETS (16)

#define GETHOSTNAME_SLEEP_MICROSEC (10000)
#define CONNECTION_SLEEP_MICROSEC (5000)
#define SELECT_TIMEOUT_SEC (0)
#define SELECT_TIMEOUT_MICROSEC (100000)

typedef struct tagSOCK_DEAL{
	char* server;
	int port;
	ekk_bool cancel_deal;
}SOCK_DEAL;

static SOCK_DEAL sd_list[MAX_SOCKETS] = {0};
static ekk_bool g_has_gothost = ekk_false;
static struct hostent g_lasthost = {0};
static char last_server[EKK_MAX_URL_LEN] = {0};
static EKK_THREAD_HANDLE g_last_hdl = NULL;
static ekk_bool g_get_host_err = ekk_false;
static struct in_addr g_host_in_addr = {0};
static struct in_addr *g_shit_ptr = NULL;

static void* get_host_by_name_thread(void *param)
{
    struct hostent* host = gethostbyname(last_server);
    if (host == NULL)
    {
        g_get_host_err = ekk_true;
    }
    if (!g_get_host_err)
    {
        ekk_memcpy(&g_lasthost,host,sizeof(struct hostent));
        ekk_memcpy(&g_host_in_addr,host->h_addr,sizeof(struct in_addr));
        g_shit_ptr = &g_host_in_addr;
        g_lasthost.h_addr_list = &g_shit_ptr;
        //freehostent(host);
    }
    g_has_gothost = ekk_true;
    return NULL;
}

static ekk_bool get_and_cache_lasthost(char* server, struct hostent** p_host)
{
    void* ret_val = 0;
    if (!last_server[0] || ekk_strcmp(last_server, server)!= 0 || g_get_host_err)
    {
        if (g_last_hdl)
        {
            ekk_thread_join(g_last_hdl, &ret_val);
            g_last_hdl = NULL;
        }
        strcpy(last_server, server);
        g_get_host_err = ekk_false;
        g_has_gothost = ekk_false;
        //create a thread to get host
        g_last_hdl = ekk_thread_create(get_host_by_name_thread, NULL);
        printf("REGET HOSTNAME\n");
    }
    if (g_has_gothost || g_get_host_err)
    {
        if (g_last_hdl)
        {
            ekk_thread_join(g_last_hdl, &ret_val);
            g_last_hdl = NULL;
        }
        if (g_get_host_err)
        {
            *p_host = NULL;
        }
        else
        {
            g_shit_ptr = &g_host_in_addr;
            g_lasthost.h_addr_list = &g_shit_ptr;
            *p_host = &g_lasthost;
        }
        return ekk_true;
    }
    *p_host = NULL;
    return ekk_false;
}

void ekk_socket_deal(char* server,int port,char* sendbuf,int* sendlen,char* recvbuf,int* recvlen){
	int sockfd;
	struct hostent *host;
	struct sockaddr_in serv_addr;
	int esendlen;
	int iLength;
	int iEnd=0;
	int i = 0;
	int i_in_list = 0;
	int flag = 0;
	int connect_ok = 0;
	int ret_t = 0;
	fd_set rset = {0},wset = {0};
	int error;
	socklen_t len;
	struct timeval tval;
	int ssendlen = 0;
	
	//add to list
	for (i = 0; i < MAX_SOCKETS; ++i)
	{
		if (sd_list[i].server == NULL)
		{
			sd_list[i].server = server;
			sd_list[i].port = port;
			i_in_list = i;
			break;
		}
	}
    
    while (!get_and_cache_lasthost(server, &host) && !sd_list[i_in_list].cancel_deal)
    {
        ekk_sleep(GETHOSTNAME_SLEEP_MICROSEC);
    }
	if(host==NULL)
    {
		printf("gethostbyname error");
		return;
	}
     //printf("GOT HOST SUCCEED! name = %s, %x\n",host->h_name, host->h_addr);
	
DO_DEAL:
	if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
		printf("socket error");
		return;
	}
	serv_addr.sin_family=AF_INET;
	serv_addr.sin_port=htons(port);
	serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
	bzero(&(serv_addr.sin_zero),8);
	
	
	//set socket to nonblock
	flag = fcntl(sockfd, F_GETFL, 0);
	fcntl(sockfd, F_SETFL, flag|O_NONBLOCK);
	
	//try connect
	if ( ( ret_t =connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr)) ) <0 )
	{
		/*if(errno!=EINPROGRESS)
			goto SD_CLEAN;
		else
		{
			printf("connect fail err=%d \n",errno);
		}*/

	}
	if(ret_t == 0)
	{
		EKKLD("connect completed immediately");
		connect_ok = 1;
	}
	while (connect_ok != 1 && !sd_list[i_in_list].cancel_deal)
	{
		FD_ZERO(&rset);
		FD_SET(sockfd,&rset);
		wset=rset;
		tval.tv_sec=SELECT_TIMEOUT_SEC;
		tval.tv_usec=SELECT_TIMEOUT_MICROSEC;
		
		if(( ret_t = select(sockfd+1,&rset,&wset,NULL,&tval)) < 0)
		{
			EKKLD("time out connect error");
			close(sockfd);
			goto DO_DEAL;
		}
		if (ret_t == 0)
		{
			continue;
		}
		if ( FD_ISSET(sockfd,&rset) || FD_ISSET(sockfd,&wset) )
		{
			len = sizeof(error);
			if( getsockopt(sockfd,SOL_SOCKET,SO_ERROR,&error,&len) <0)
				goto SD_CLEAN;
			if (error == ECONNRESET)
			{
				close(sockfd);
				goto DO_DEAL;
			}
		}
		while (!sd_list[i_in_list].cancel_deal)
		{
			ret_t = connect(sockfd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr));
			switch(errno)
			{
				case EISCONN:   //connect ok
					EKKLD("connect OK \n");
					connect_ok = 1;
					break;
				case EALREADY:
					connect_ok = 0;
					break;
				case EINPROGRESS:// is connecting, need to check again
					connect_ok = 0;
					break;
				default:
					EKKLD("connect fail err=%d \n",errno);

					connect_ok = 0;
					close(sockfd);
					goto DO_DEAL;
					break;
			}
			if(connect_ok != 0)
			{
				break;
			}
			ekk_sleep(CONNECTION_SLEEP_MICROSEC);
		}
		
	}

	if (connect_ok == 1)
	{
		//do deals here
		
		//sending
		ssendlen = send(sockfd,sendbuf,*sendlen,0);
		if (ssendlen != *sendlen) {
			EKKLD("\n send error have sent = %d",ssendlen);
			goto SD_CLEAN;
		}
		EKKLD("have sent: %s",sendbuf);
		
		//receiving
		
		while (!sd_list[i_in_list].cancel_deal)
		{
			//beyond the protocol packet get packet size.
			FD_ZERO(&rset);
			FD_SET(sockfd,&rset);//如果前面select使用了rset，最好重新赋值
			if( ( ret_t = select(sockfd+1,&rset,NULL,NULL,&tval)) <=0 )
			{
				goto SD_CLEAN;
			}
			if (ret_t == 0)
			{
				continue;
			}
			if ((iLength=recv(sockfd,recvbuf,*recvlen,0)) ==-1)
			{
				EKKLE("recv error!");
				goto SD_CLEAN;
			}
			*recvlen = iLength;
            break;
			EKKLD("received: %d ",iLength);
		}
	}
	
SD_CLEAN:
	close(sockfd);

	//clear data in list
	sd_list[i_in_list].port = 0;
	sd_list[i_in_list].server = NULL;
	sd_list[i_in_list].cancel_deal = ekk_false;
}

void ekk_cancel_sock_deal(char* server,int port)
{
	int i = 0;
	for (i = 0; i < MAX_SOCKETS; ++i)
	{
		if (sd_list[i].port > 0 && sd_list[i].server && ekk_strcmp(sd_list[i].server,server) == 0 && sd_list[i].port == port)
		{
			sd_list[i].cancel_deal = ekk_true;
		}
	}
}

#endif /*#if EKK_ENABLE_SUBTITLE*/